package com.dddframework.kit.thread;

import com.dddframework.core.context.SpringContext;
import com.dddframework.core.utils.BizAssert;
import com.dddframework.core.utils.JsonKit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.autoconfigure.task.TaskExecutionProperties;
import org.springframework.boot.autoconfigure.task.TaskSchedulingProperties;
import org.springframework.boot.task.TaskExecutorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import java.util.Date;
import java.util.Objects;
import java.util.concurrent.*;

/**
 * ThreadPoolKit
 * <p>
 * 线程池工具类
 */
@EnableAsync
@EnableScheduling
@Configuration(proxyBeanMethods = false)
@RequiredArgsConstructor
@Slf4j(topic = "### BASE-KIT : ThreadPoolKit ###")
public class ThreadPoolKit implements InitializingBean {
    final TaskExecutionProperties taskExecutionProperties;
    final TaskSchedulingProperties taskSchedulingProperties;

    @Override
    public void afterPropertiesSet() {
        initTaskExecutionProperties(taskExecutionProperties);
        initTaskSchedulingProperties(taskSchedulingProperties);
    }

    /**
     * 初始化线程池默认参数
     */
    private void initTaskExecutionProperties(TaskExecutionProperties taskExecutionProperties) {
        TaskExecutionProperties.Pool defaultConfig = new TaskExecutionProperties.Pool();
        TaskExecutionProperties.Pool pool = taskExecutionProperties.getPool();
        if (pool.getCoreSize() == defaultConfig.getCoreSize()) {
            pool.setCoreSize(Runtime.getRuntime().availableProcessors() * 2);
        }
        if (pool.getQueueCapacity() == defaultConfig.getQueueCapacity()) {
            pool.setQueueCapacity(500);
        }
        if (pool.getMaxSize() == defaultConfig.getMaxSize()) {
            pool.setMaxSize(200);
        }
        log.debug("TaskExecutionProperties: " + JsonKit.toJson(pool));
    }


    /**
     * 初始化调度器默认参数
     */
    private void initTaskSchedulingProperties(TaskSchedulingProperties taskSchedulingProperties) {
        TaskSchedulingProperties.Pool defaultConfig = new TaskSchedulingProperties.Pool();
        TaskSchedulingProperties.Pool pool = taskSchedulingProperties.getPool();
        if (pool.getSize() == defaultConfig.getSize()) {
            pool.setSize(4);
        }
        log.debug("TaskSchedulingProperties: " + JsonKit.toJson(pool));
    }

    /**
     * applicationTaskExecutor
     */
    @Bean(name = {TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME,
            AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME})
    public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
        return builder.build();
    }

    private static final String APPLICATION_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";
    private static ThreadPoolTaskExecutor applicationTaskExecutor;
    private static ThreadPoolTaskScheduler applicationTaskScheduler;


    private static ThreadPoolTaskExecutor getApplicationTaskExecutor() {
        if (Objects.isNull(applicationTaskExecutor)) {
            applicationTaskExecutor = SpringContext.getApplicationContext().getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, ThreadPoolTaskExecutor.class);
        }
        return applicationTaskExecutor;
    }

    public static ThreadPoolTaskExecutor get(String threadPoolName) {
        return SpringContext.getApplicationContext().getBean(threadPoolName, ThreadPoolTaskExecutor.class);
    }

    private static ThreadPoolTaskScheduler getApplicationTaskScheduler() {
        if (Objects.isNull(applicationTaskScheduler)) {
            applicationTaskScheduler = SpringContext.getApplicationContext().getBean(APPLICATION_TASK_SCHEDULER_BEAN_NAME, ThreadPoolTaskScheduler.class);
        }
        return applicationTaskScheduler;
    }

    public static void execute(Runnable task) {
        getApplicationTaskExecutor().execute(task);
    }

    public static Future<?> submit(Runnable task) {
        return getApplicationTaskExecutor().submit(task);
    }

    public static <V> Future<V> submit(Callable<V> task) {
        return getApplicationTaskExecutor().submit(task);
    }

    public static ScheduledFuture<?> schedule(Runnable task, Date startTime) {
        return getApplicationTaskScheduler().schedule(task, startTime);
    }

    public static ScheduledFuture<?> schedule(Runnable task, int delayTimeMillis) {
        Date startTime = new Date(System.currentTimeMillis() + delayTimeMillis);
        return schedule(task, startTime);
    }

    /**
     * 执行任务（可为目标任务单独指定拒绝策略）
     */
    public static void execute(Runnable task, RejectedExecutionHandler rejectedExecutionHandler) {
        BizAssert.notNull(rejectedExecutionHandler);
        try {
            execute(task);
        } catch (RejectedExecutionException e) {
            rejectedExecutionHandler.rejectedExecution(task, getApplicationTaskExecutor().getThreadPoolExecutor());
        }
    }

    /**
     * 执行任务（当线程池拒绝执行时是否由当前线程执行）
     */
    public static void execute(Runnable task, boolean callerRunsPolicy) {
        if (callerRunsPolicy) {
            execute(task, new ThreadPoolExecutor.CallerRunsPolicy());
        } else {
            execute(task);
        }
    }

}
